//进程间通信（IPC）-> 无名管道
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>

/*
底层原理：
    内核管理的环形缓冲区(通常 4KB 或 64KB 环状缓冲区);
    通过 pipe()系统调用创建，返回两个文件描述符(读端/写端),fd[0]读端, fd[1]写端,父子进程继承fd.
    数据单向流动, 先进先出(FIFO)
使用场景：
    父子进程间简单通信
    Shell 命令管道（cmd1 | cmd2）
优点：
    简单、高效 (内核缓冲区拷贝)
    简单易用
    无需同步机制
    自动流量控制
缺点：
    只能用于有亲缘关系的进程
    单向通信
    数据无结构(字节流)
    缓冲区大小有限
数据读取以及时效性：
    消费型读取,数据一旦被读取,就从缓冲区移除,天然保证顺序和最新性,读端阻塞等待新数据。
注意事项：
    写端关闭后读端返回 EOF
    读端关闭后写端会收到 SIGPIPE
    原子写入限制（Linux 约 4096 字节）
*/
void sigchld_handler(int signum){
    printf("signum = [%d]\n", signum);
    /*
        -1: 回收任意一个子进程
        NULL：子进程退出原因不在乎
        WNOHANG：如果没有子进程退出，立即返回
        返回值：>0: 回收成功的子进程id, ==0:子进程还在运行中, <0 出现错误
    */
    pid_t pid;
    while((pid = waitpid(-1, NULL, WNOHANG)) > 0){ 
        printf("回收成功：pid = [%d]\n", pid);
    }
    
    return;
}

// 处理 SIGPIPE 信号
void sigpipe_handler(int signum) {
    printf("收到 SIGPIPE 信号，父进程退出\n");
    //exit(-1);   使整个进程退出
    return;     //返回调用处继续向下执行
}

int main(int argc, char *argv[]){
    int pfd[2];
    pid_t pid;

    int ret = pipe(pfd);                 //管道的文件描述符：fd[0]为读端，fd[1]为写端
    if(ret < 0){
        perror("pipe(): ");
        return -1;
    }

    pid = fork();
    if(pid < 0){
        perror("fork(): ");
        return -1;
    }
    
    if(pid == 0){ 
        close(pfd[1]);                    //子进程读数据，关闭写端
        
        char recvBuf[1024] = {0};
        while(1){
            memset(recvBuf, 0, sizeof(recvBuf));
            ret = read(pfd[0], recvBuf, sizeof(recvBuf));
            if(ret < 0){
                perror("read():");
                break;
            }
            printf("child: ret = [%d], buf = [%s]\n",ret, recvBuf);
            if(!strncmp(recvBuf, "exit", 4)){
                break;
            }
        }
        printf("子进程退出\n");
        close(pfd[0]);                    //读完后关闭读端
    }else{
        signal(SIGCHLD, sigchld_handler); // 注册 SIGCHLD 信号处理函数, 回收子进程
        signal(SIGPIPE, sigpipe_handler); // 注册 SIGPIPE 信号处理函数
        close(pfd[0]);                    //父进程写数据，关闭读端        
        char sendBuf[1024] = {0};
        
        while(1){
            memset(sendBuf, 0, sizeof(sendBuf));
            fscanf(stdin, "%s", sendBuf);
            ret = write(pfd[1], sendBuf, strlen(sendBuf) + 1);
            if(ret < 0){
                perror("write():");
                break;
            }
        }
        printf("父进程退出\n");
        close(pfd[1]);                    //写完后关闭写端
    }
    
    return 0;
}
